package conn

import (
	"bufio"
	"context"
	"errors"
	"log"
	"net"
	"strings"
	"sync"
	"time"
)

// Conn
// 数据通讯有2种方式,
// 1.采用起始帧+数据域+结束帧
// 2.自定义数据格式解析,发送数据也自定义
// tcp连接,长连接,默认不主动关闭
// 连接成功后,通过心跳包(数据)保持连接
type Conn struct {
	buf        *bufio.Reader //io.Reader
	net.Conn                 //tcp连接,默认远程地址作为唯一标识符
	tag        string        //标签
	key        string        //主键key
	localAddr  Addr          //本地地址
	remoteAddr Addr          //远程地址
	mu         sync.Mutex    //互斥锁,锁住出现错误时的后续操作

	writeChan chan []byte                 //写出数据通道,所有发送数据都经过这里
	readChan  chan []byte                 //读数据通道,可以刷新超时
	readFunc  func(*Conn) ([]byte, error) //字节读取的规则
	dealFunc  []func(*Msg)                //处理读取到的数据包
	closeFunc func(*Msg)                  //结束信号,链接关闭触发事件
	printFunc func(string, *Msg)          //打印数据方法
	sendFunc  func([]byte) []byte         //数据发送函数,包装下原始数据

	err         error         //错误信息,退出信号
	debug       bool          //调试,是否打印数据
	timeout     time.Duration //超时时间,时间范围内没有发送数据或者接收数据,则断开链接
	timeoutRead time.Duration //读取字节超时时间,时间范围内没有新的字节则截断读取,返回数据包,不会报错退出
	timeoutSend time.Duration //发送数据超时时间,发送数据超时则会返回超时错误,并断开连接
	keepAlive   time.Duration //保持连接

	createTime   time.Time //创建时间,链接成功时间
	lastReadTime time.Time //最后读取到远程数据时间
	lastSendTime time.Time //最后发送到远程数据时间
	lastTime     time.Time //最后通讯时间(读取或者发送)

	closeAll bool //全部关闭,主要是为了,为了区别主动关闭和错误关闭
	running  bool //是否已经执行,防止重复执行
	cancel   context.CancelFunc
	ctx      context.Context
}

// New 新建连接实例,
// 读写分离,双工,共3个协程(监听数据,写入数据,检测超时)
// todo 后续增加半双工,只开1个协程
func New(c net.Conn) *Conn {
	return NewWithContext(context.Background(), c)
}

func NewWithContext(ctx context.Context, c net.Conn) *Conn {
	data := &Conn{
		buf:         bufio.NewReader(c),
		Conn:        c,
		key:         c.RemoteAddr().String(),
		localAddr:   NewAddr(c.LocalAddr()),
		remoteAddr:  NewAddr(c.RemoteAddr()),
		writeChan:   make(chan []byte, 1),
		readChan:    make(chan []byte, 1),
		readFunc:    nil,
		dealFunc:    nil,
		closeFunc:   DefaultCloseFunc,
		printFunc:   DefaultPrintFunc,
		sendFunc:    DefaultSendFunc,
		debug:       false,
		timeout:     DefaultTimeout,
		timeoutRead: DefaultTimeoutRead,
		timeoutSend: DefaultTimeoutSend,
		createTime:  time.Now(),
	}
	data.ctx, data.cancel = context.WithCancel(ctx)
	return data
}

// Running
// 是否已经执行
// 监听和消息发送等功能
// 多次运行只会执行1次,
// 后续会等待关闭信号然后返回错误,
// 错误赋值在关闭信号之前
func (this *Conn) Running() bool {
	return this.running
}

// Err 获取错误
// 错误赋值优先于关闭信号
func (this *Conn) Err() error {
	return this.err
}

// Ctx 上下文
func (this *Conn) Ctx() context.Context {
	return this.ctx
}

// Done 关闭信号有关闭关闭信号,一定有错误
func (this *Conn) Done() <-chan struct{} {
	return this.ctx.Done()
}

// SignClose 关闭信号有关闭关闭信号,一定有错误
func (this *Conn) SignClose() <-chan struct{} {
	return this.ctx.Done()
}

// GetLocal 获取本地连接Addr
func (this *Conn) GetLocal() Addr {
	return this.localAddr
}

// GetLocalAddr 获取本地连接地址,例127.0.0.1:37685
func (this *Conn) GetLocalAddr() string {
	return this.localAddr.Addr()
}

// GetLocalIP 获取本地连接ip,例127.0.0.1
func (this *Conn) GetLocalIP() string {
	return this.localAddr.IP()
}

// GetRemote 获取远程地址Addr
func (this *Conn) GetRemote() Addr {
	return this.remoteAddr
}

// GetRemoteAddr 获取远程地址
func (this *Conn) GetRemoteAddr() string {
	return this.remoteAddr.Addr()
}

// GetRemoteIP 获取远程地址ip
func (this *Conn) GetRemoteIP() string {
	return this.remoteAddr.IP()
}

// GetTag
// 获取标签,标签有用户自定义,
func (this *Conn) GetTag() string {
	return this.tag
}

// SetTag
// 设置标签
func (this *Conn) SetTag(tag string) *Conn {
	this.tag = tag
	return this
}

// GetKey
//获取主键,默认使用远程地址
func (this *Conn) GetKey() string {
	return this.key
}

// SetKey
// 设置主键,可自定义主键,
// 方便查找链接
// 实现对应的业务逻辑,为了唯一性一般使用远程地址
func (this *Conn) SetKey(key string) *Conn {
	this.key = key
	return this
}

// GetCreateTime 获取创建(建立连接)时间
func (this *Conn) GetCreateTime() time.Time {
	return this.createTime
}

// GetLastReadTime 获取最后读取数据时间
func (this *Conn) GetLastReadTime() time.Time {
	return this.lastReadTime
}

// GetLastSendTime 获取最后发送数据时间
func (this *Conn) GetLastSendTime() time.Time {
	return this.lastSendTime
}

// GetLastTime 获取最后通讯时间
func (this *Conn) GetLastTime() time.Time {
	return this.lastTime
}

// GetResponseTime 获取响应耗时,
// 如果发送后调用,则是本地响应时间
// 如果是读取数据后调用,则是远程响应时间
func (this *Conn) GetResponseTime() time.Duration {
	sub := this.lastReadTime.Sub(this.lastSendTime)
	if sub < 0 {
		sub = -sub
	}
	return sub
}

// SetReadFunc
// 设置自定义读取数据
// 默认解析方式无效,数据发送也自定义
func (this *Conn) SetReadFunc(fn func(*Conn) (bytes []byte, err error)) *Conn {
	this.readFunc = fn
	return this
}

// AddDealFunc
// 设置自定义数据使用
// 设置读取到数据包的后续操作
// 如果未设置,则舍弃数据包
func (this *Conn) AddDealFunc(fn ...func(*Msg)) *Conn {
	for _, v := range fn {
		if v != nil {
			this.dealFunc = append(this.dealFunc, func(msg *Msg) {
				defer func() {
					if err := recover(); err != nil {
						log.Println("[错误][CONN]", err)
					}
				}()
				v(msg)
			})
		}
	}
	return this
}

// SetDealFunc
// 设置自定义数据使用
// 设置读取到数据包的后续操作
// 如果未设置,则舍弃数据包
func (this *Conn) SetDealFunc(fn ...func(*Msg)) *Conn {
	this.dealFunc = nil
	this.AddDealFunc(fn...)
	return this
}

// SetCloseFunc
// 设置结束信号
// 链接关闭时,会触发该方法,
// 参数带有链接的主键和关闭原因(错误信息)
func (this *Conn) SetCloseFunc(fn func(*Msg)) *Conn {
	this.closeFunc = fn
	return this
}

// SetPrintFunc
// 设置打印方法
// 设置日志格式,一般选择默认方法
func (this *Conn) SetPrintFunc(fn func(string, *Msg)) *Conn {
	this.printFunc = fn
	return this
}

// SetPrintWithHEX 设置打印方式HEX
func (this *Conn) SetPrintWithHEX() *Conn {
	this.printFunc = PrintWithHEX
	return this
}

// SetPrintWithASCII 设置打印方式ASCII
func (this *Conn) SetPrintWithASCII() *Conn {
	this.printFunc = PrintWithASCII
	return this
}

// SetSendFunc 设置发送包装方法
func (this *Conn) SetSendFunc(fn func([]byte) []byte) *Conn {
	this.sendFunc = fn
	return this
}

// SetKeepAlive 设置连接保持,另外起了携程,服务器不需要,客户端再起一个也没啥问题
func (this *Conn) SetKeepAlive(t time.Duration, keeps ...[]byte) *Conn {
	keep := []byte(DefaultPing)
	if len(keeps) > 0 {
		keep = keeps[0]
	}
	old := this.keepAlive
	this.keepAlive = t
	if old == 0 && this.keepAlive > 0 {
		go func(ctx context.Context) {
			for {
				select {
				case <-ctx.Done():
					return
				default:
					if this.keepAlive <= 0 {
						return
					}
					<-time.After(this.keepAlive)
					if err := this.SendBytes(keep); err != nil {
						return
					}
				}
			}
		}(this.ctx)
	}
	return this
}

// SetTimeout
// 设置超时时间,
// 默认不超时,0默认不超时
// 作为服务器时一定要设置超时时间,过滤掉无效链接
func (this *Conn) SetTimeout(t time.Duration) *Conn {
	this.timeout = t
	return this
}

// SetTimeoutRead
// 设置超时读取时间,默认不超时
// 默认读取数据时间,如果超时,则会返回错误
// 最佳方式是采用起始帧加结束帧的方式,
// 第二优先结束帧(需要验证数据包的完整性)
// 如只设置了起始帧,并不确定结束位置时,超时会截断等待数据并返回
func (this *Conn) SetTimeoutRead(t time.Duration) *Conn {
	this.timeoutRead = t
	return this
}

// SetTimeoutSend
// 设置超时写数据时间,默认不超时
func (this *Conn) SetTimeoutSend(t time.Duration) *Conn {
	this.timeoutSend = t
	return this
}

// Send
// 发送数据,字符串
// 采用ASCII码转字节数组
func (this *Conn) Send(s string) error {
	return this.SendBytes([]byte(s))
}

// SendString
// 发送数据,字符串
// 采用ASCII码转字节数组
func (this *Conn) SendString(s string) error {
	return this.SendBytes([]byte(s))
}

// SendBytes 发送数据,字节数组
func (this *Conn) SendBytes(bs []byte) error {
	if !this.Running() {
		go this.Run()
	}
	select {
	case <-this.ctx.Done():
		return this.err
	case this.writeChan <- bs:
	}
	return nil
}

// Close 用户主动关闭连接,默认错误(主动关闭链接)
func (this *Conn) Close(err ...error) {
	this.closeAll = true
	if len(err) != 0 {
		this.closeErr(err[0])
	} else {
		this.closeErr(errors.New("主动关闭连接"))
	}
}

// Debug 调试模式,打印接收和发送数据
func (this *Conn) Debug(b ...bool) *Conn {
	this.debug = !(len(b) > 0 && !b[0])
	return this
}

// Read io.Reader
func (this *Conn) Read(p []byte) (int, error) {
	return this.buf.Read(p)
}

// ReadOne 读取一字节
func (this *Conn) ReadOne() (byte, error) {
	return this.buf.ReadByte()
}

// Redial 重试连接
func (this *Conn) Redial(fn ...func(c *Conn)) *Conn {
	return this.redial(fn...)
}

// Run 运行
// 可以多次运行,只有第一次执行有效
// 后续执行会等待关闭信号,然后返回错误
func (this *Conn) Run() error {
	if this.Running() {
		select {
		case <-this.ctx.Done():
			return this.err
		}
	}
	this.running = true
	return this.run()
}

//=====================================不公开==================================//

// closeErr 遇到错误关闭
// 关闭连接,不会覆盖错误,防止多线程多个错误
func (this *Conn) closeErr(err error) {
	this.mu.Lock()
	if err != nil && this.err == nil {
		this.err = dealErr(err)
		//触发关闭链接事件
		if this.closeFunc != nil {
			defer this.closeFunc(NewMsg(this, []byte(this.err.Error())))
		}
		//上下文
		this.cancel()
		//自动回收
		//close(this.writeChan)
		//关闭net.Conn连接
		if err := dealErr(this.Conn.Close()); err != nil && err != ErrCloseClose {
			log.Println("关闭连接错误:", err)
		}
	}
	this.mu.Unlock()
}

//  run 运行
func (this *Conn) run() error {
	go this.runRead(this.ctx)
	timer := time.NewTimer(time.Second * 5)
	defer timer.Stop()
	go func(ctx context.Context) {
		for {
			if this.timeout <= 0 {
				timer.Reset(time.Second * 5)
			} else {
				timer.Reset(this.timeout)
			}
			select {
			case <-ctx.Done():
				return
			case bs := <-this.writeChan:
				if bs != nil {
					//发送数据
					this.closeErr(this.sendBytes(bs))
					t := time.Now()
					this.lastSendTime = t
					this.lastTime = t
				}
			case <-timer.C:
				//超时
				if this.timeout > 0 {
					this.closeErr(errors.New("超时断开连接"))
				} else {
					_ = this.Conn.SetDeadline(time.Now().Add(time.Minute * 2))
				}
			}
		}
	}(this.ctx)
	for i := 0; ; i++ {
		if this.timeout <= 0 {
			timer.Reset(time.Second * 5)
		} else {
			timer.Reset(this.timeout)
		}
		select {
		case <-this.ctx.Done():
			return this.err
		case bs := <-this.readChan:
			if bs != nil {
				for _, fn := range this.dealFunc {
					if fn != nil {
						fn(NewMsg(this, bs))
					}
				}
			}

		case <-timer.C:
			//超时
			if this.timeout > 0 {
				this.closeErr(errors.New("超时断开连接"))
			} else {
				_ = this.Conn.SetDeadline(time.Now().Add(time.Minute * 2))
			}
		}
	}
}

//  runRead 持续接受数据
func (this *Conn) runRead(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		default:
		}
		bs, err := this.readBytes()
		if err != nil {
			this.closeErr(err)
		} else if len(bs) > 0 {
			t := time.Now()
			this.lastReadTime = t
			this.lastTime = t
			//打印读取数据内容
			if this.debug && this.printFunc != nil {
				this.printFunc("接收", NewMsg(this, bs))
			}
			this.readChan <- bs
		}
	}
}

// sendBytes 发送数据,字节,内置方法
func (this *Conn) sendBytes(bs []byte) (err error) {
	this.mu.Lock()
	defer this.mu.Unlock()
	if this.err == nil {
		if this.sendFunc != nil {
			bs = this.sendFunc(bs)
		}
		//打印发送数据内容
		if this.debug && this.printFunc != nil {
			this.printFunc("发送", NewMsg(this, bs))
		}
		//设置发送数据超时时间,0不做超时
		if this.timeoutSend > 0 {
			_ = this.Conn.SetWriteDeadline(time.Now().Add(this.timeoutSend))
		}
		_, err := this.Conn.Write(bs)
		return err
	}
	return this.err
}

// readBytes 读取数据,字节
func (this *Conn) readBytes() (bytes []byte, err error) {
	//自定义读取数据超时时间,设置后会源源不断的返回空数据,并被过滤
	var valid time.Time
	if this.timeoutRead > 0 {
		valid = time.Now().Add(this.timeoutRead)
	}
	if err = this.Conn.SetReadDeadline(valid); err != nil {
		return
	}
	//设置默认读取数据方法
	readFunc := this.readFunc
	if readFunc == nil {
		readFunc = DefaultReadFunc
	}
	bytes, err = readFunc(this)
	if err != nil && strings.Contains(err.Error(), "timeout") {
		err = nil
	}
	return
}

// redial 重试连接
func (this *Conn) redial(fn ...func(c *Conn)) *Conn {
	this.SetCloseFunc(func(msg *Msg) {
		if this.closeAll {
			log.Printf("[信息][%s] 连接断开(%s)", msg.Key(), msg)
			return
		}
		log.Printf("[信息][%s] 连接断开(%s),尝试重新连接\n", msg.Key(), msg)
		time.Sleep(time.Second)
		*this = *MustDial(this.GetLocal().Network(), this.GetRemoteAddr())
		this.Redial(fn...)
	})
	for _, v := range fn {
		v(this)
	}
	go this.Run()
	return this
}
